package com.urbainski.componentes.form
{
	
	import com.urbainski.componentes.form.ControlsHelper;
	import com.urbainski.componentes.form.MyForm;
	
	import flash.events.Event;
	
	import mx.core.IDataRenderer;
	import mx.core.UIComponent;
	import mx.events.FlexEvent;
	import mx.validators.IValidator;
	import mx.validators.Validator;
	
	public class MyDataFormItem implements IDataRenderer
	{
	
		/**
		 * @author Cristian Elder Urbainski
		 * @since 20/12/2011
		 */
		
		private var _component:UIComponent;
		
		public function MyDataFormItem(parent:UIComponent)
		{
			_component = parent;
		}
		
		[Inspectable(defaultValue="com.consisa.mx.components.supportClasses.ControlsHelper")]
		
		/**
		 *  Specifies the class that defines a custom helper.
		 * 
		 *  <p><b>Note:</b> This property must be set before the dispatching
		 *  of FlexEvent.INITIALIZE event by the FormItem.</p>
		 *  
		 */
		public var controlsHelperClass:Class = ControlsHelper;
		
		//----------------------------------
		//  controlsHelper
		//----------------------------------
		
		/**
		 *  @private
		 *  Storage for the controlsHelper property.
		 */
		private var _controlsHelper:ControlsHelper;
		private var controlsHelperChanged: Boolean;
		
		private var controlChanged:Boolean = false;
		
		/**
		 * 
		 *  Helper object which connects a control with the DataFormItem.
		 *  It applies a new distributed value and handles any changes
		 *  made by the user.
		 */
		public function get controlsHelper():ControlsHelper
		{
			return _controlsHelper;
		}
		
		public function set controlsHelper(value: ControlsHelper):void
		{
			if (value == null)
				throw new ArgumentError("controlsHelper is not" +
					" clear.containers.supportCLasses.dataForm.ControlsHelper.");
			
			if (_controlsHelper == value)
				return;
			
			if (_controlsHelper)
			{
				// Before first successful setter call _controlsHelper is null, so need to check
				if (_controlsHelper.connected)
					_controlsHelper.releaseControl();
			}
			
			_controlsHelper = value;
			
			var clazz:Class = Object(_controlsHelper).constructor;
			if (controlsHelperClass != clazz)
				controlsHelperClass = clazz;
			
			controlsHelperChanged = true;
			_component.invalidateProperties();
		}
		
		//----------------------------------------------------------------------
		//
		//  Validation Properties
		//
		//----------------------------------------------------------------------
		
		//----------------------------------
		//  validators
		//----------------------------------
		
		/**
		 *  @private
		 *  Storage for the validators property.
		 */
		private var _validators:Array;
		private var validatorsChanged:Boolean;
		
		[Bindable("validatorsChange")]
		[InstanceType("mx.validators.Validator")]
		[Inspectable(category="Validation")]
		
		/**
		 * 	Array of validators which are related to this FormItem.
		 *  If this FormItem is added as a child by DataForm,
		 * 	its validators will be registered in DataForm component.
		 *  Also, their <code>trigger</code> and <code>triggerEvent</code>
		 *  properties will be updated if a developer explicitly set
		 *  the same properties for the DataForm.
		 * 
		 *  <p>This property is Bindable; it dispatches 
		 *  "validatorsChange" events</p>
		 * 
		 *  @default null
		 *  @eventType validatorsChange 
		 * 
		 */
		public function get validators():Array
		{
			return _validators;
		}
		
		/**
		 *  @public
		 */
		public function set validators(value:Array):void
		{
			if (_validators == value) return;
			
			var validator:Validator;
			
			if (_component.parent && _component.parent is MyForm)
				MyForm(_component.parent).unregisterValidators(_validators);
			
			if (_validators)
				for each (validator in _validators)
			{
				validator.property = null;
				validator.source = null;
				validator.trigger = null;
				validator.triggerEvent = FlexEvent.VALUE_COMMIT;
			}
			
			_validators = value;
			
			if (_component.parent && _component.parent is MyForm)
				MyForm(_component.parent).registerValidators(_validators);
			
			validatorsChanged = true;
			_component.invalidateProperties();
			
			if (_component.hasEventListener("validatorsChange"))
				_component.dispatchEvent(new Event("validatorsChange"));
		}
		
		//----------------------------------
		//  targetProperty
		//----------------------------------
		
		/**
		 *  @private
		 *  Storage for the targetProperty property.
		 */
		private var _targetProperty:String;
		private var targetPropertyChanged:Boolean;
		
		[Bindable("targetPropertyChange")]
		[Inspectable(category="Data")]
		
		/**
		 *  
		 *  Use this proeprty to specify which filed in the connected control
		 *  <code>controlsHelper</code> should update or listen for updates of the data.
		 *
		 *  @default null
		 *  @eventType targetPropertyChange 
		 * 
		 */
		public function get targetProperty():String
		{
			return _targetProperty;
		}
		
		/**
		 *  @private
		 */
		public function set targetProperty(value:String):void
		{
			if (_targetProperty == value) return;
			
			_targetProperty = value;
			
			targetPropertyChanged = true;
			_component.invalidateProperties();
			
			if (_component.hasEventListener("targetPropertyChange"))
				_component.dispatchEvent(new Event("targetPropertyChange"));
		}
		
		//----------------------------------
		//  eventType
		//----------------------------------
		
		/**
		 *  @private
		 *  Storage for the eventType property.
		 */
		private var _eventType:String;
		private var eventTypeChanged:Boolean;
		
		[Bindable("eventTypeChange")]
		[Inspectable(category="Data")]
		
		/**
		 *  
		 *  Use this proeprty to specify which event dispatched by the connected control
		 *  <code>controlsHelper</code> should listen for data update.
		 *
		 *  @default null
		 *  @eventType eventTypeChange 
		 * 
		 */
		public function get eventType():String
		{
			return _eventType;
		}
		
		/**
		 *  @private
		 */
		public function set eventType(value:String):void
		{
			if (_eventType == value) return;
			
			_eventType = value;
			
			eventTypeChanged = true;
			_component.invalidateProperties();
			
			if (_component.hasEventListener("eventTypeChange"))
				_component.dispatchEvent(new Event("eventTypeChange"));
		}
		
		
		//----------------------------------------------------------------------
		//
		//  Overriden Methods
		//
		//----------------------------------------------------------------------
		
		/**
		 *  @private
		 */
		public function initialize():void
		{
			
			// Instantiate helper
			if (controlsHelperClass
				&& (!controlsHelper || controlsHelperClass != Object(controlsHelper).constructor))
				controlsHelper = new controlsHelperClass() as ControlsHelper;
		}
		
		/**
		 *  @private
		 */
		public function commitProperties():void
		{ 		
			
			var valueWasModified:Boolean = false;
			
			if (controlsHelper) {
				if (controlsHelperChanged || targetPropertyChanged)
					controlsHelper.targetProperty = targetProperty;
				
				if (controlsHelperChanged || eventTypeChanged)
					controlsHelper.eventType = eventType;
				
				// If value or valueType changed, we need redistribute it to the first control.
				if (controlsHelperChanged)
				{
					// Check if the helper is connected ...
					if (!controlsHelper.connected) {
						controlChanged = initControl();
					}
					
				}
				
				if ((controlsHelperChanged || controlChanged || validatorsChanged || targetPropertyChanged || eventTypeChanged)
					&& validators && controlsHelper.connected && controlsHelper.eventType && controlsHelper.targetProperty) {
					// If we have validators we should configure them.
					for each (var validator:Validator in validators) {
						validator.source = controlsHelper.target;
						validator.property = controlsHelper.targetProperty;
						validator.trigger = controlsHelper.target;
						validator.triggerEvent = controlsHelper.eventType;
						validator.required = required;
					}
				}
				
				controlsHelperChanged = false;
				controlChanged = false;
				validatorsChanged = false;
				targetPropertyChanged = false;
				eventTypeChanged = false;
			}
			
			// Fix by VS: modify private state field rather than property to avoid unnecessary properties invalidation
			// This is a complementary code block to fire event if "value" was implicitly modified either by dataChange or
			// by target property change
			if (valueWasModified && _component.hasEventListener("valueChange")) {
				_component.dispatchEvent(new Event("valueChange"));
			}
			
		}
		
		/**
		 *  @private
		 *  Storage for the required property.
		 */
		private var _required:Boolean = false;
		
		[Bindable("requiredChanged")]
		[Inspectable(category="General", defaultValue="false")]
		
		/**
		 *  If <code>true</code>, display an indicator
		 *  that the FormItem children require user input.
		 *  If <code>false</code>, indicator is not displayed.
		 *
		 *  <p>This property controls the indicator display only.
		 *  You must attach a validator to the children
		 *  if you require input validation.</p>
		 *
		 *  @default false
		 *  
		 *  @langversion 3.0
		 *  @playerversion Flash 9
		 *  @playerversion AIR 1.1
		 *  @productversion Flex 3
		 */
		public function get required():Boolean
		{
			return _required;
		}
		
		/**
		 *  @private
		 */
		public function set required(value:Boolean):void
		{
			if (value != _required)
			{
				_required = value;
				
				_component.invalidateDisplayList();
				
				_component.dispatchEvent(new Event("requiredChanged"));
			}
		}
		
		private var _data : Object;
		
		public function set data(value:Object):void
		{
			_data = value;
		}
		
		public function get data():Object
		{
			return _data;
		}
		
		//----------------------------------------------------------------------
		//
		//  Methods
		//
		//----------------------------------------------------------------------
		
		/**
		 *  @private
		 */
		protected function initControl():Boolean {
			var control:UIComponent;
			var num:int;
			var i:int;
			
			// Check if there is any control to connect.
			/*if (numElements > 0)
			for (i = 0; i < numElements; i++)
			{
			var element:IVisualElement = getElementAt(i);
			if (element is UIComponent)
			{
			control = element as UIComponent;
			break;
			}
			}
			
			
			// Connect the helper and a control.
			if (control != null)*/
			controlsHelper.connectControl(_component);
			
			return controlsHelper.connected;
		}
	}
}
